<?php

class Quad_Html_Element extends Quad_Observable {

	public static $useXhtml = true;
	protected $_tagName;
	protected $_propertyMap = array('className'=>'class','htmlFor'=>'for');
	protected $_properties = array();
	protected $_attributes = array();
	protected $_childNodes = array();
	protected $_decorators = array();
	protected $_style;
	public $selfClosing = false;
	public $nodeValue = '';
	
	public function __construct($tagName=null) {
		$this->_tagName = strtolower($tagName);
		$this->_style = new Quad_Html_Element_Styles($this);
	}
	
	public function setAttributes($attributes) {
		foreach ($attributes as $a => $v) {
			$this->setAttribute($a, $v);
		}
		return $this;
	}
	
	public function setAttribute($attribute, $value) {
		if ($attribute == 'style') {
			$this->notify('setAttribute', array(
				'oldValue'=>(string)$this->_style,
				'newValue'=>$value,
			), false);
			$pairs = explode(';',$value);
			$this->_style->unsetAll();
			foreach ($pairs as $pair) {
				list ($p, $v) = explode(':',$pair);
				$p = trim($p);
				$v = trim($v);
				$this->_style->$p = $v;
			}
		}
		else {
			$this->notify('setAttribute', array(
				'oldValue'=>(isset($this->_attributes[$attribute]) ? $this->_attributes[$attribute] : null),
				'newValue'=>$value,
			), false);
			$this->_attributes[$attribute] = $value;
		}
		return $this;
	}
	
	public function getAttribute($attribute) {
		return (isset($this->_attributes[$attribute]) ? $this->_attributes[$attribute] : null);
	}
	
	public function hasAttribute($attribute) {
		return array_key_exists($attribute, $this->_attributes);
	}
	
	public function removeAttribute($name) {
		$this->notify('removeAttribute', array(
			'oldValue'=>(isset($this->_attributes[$name]) ? $this->_attributes[$name] : null),
		), false);
		unset($this->_attributes[$name]);
		return $this;
	}
	
	public function addClassName($name) {
		if (isset($this->_attributes['class'])) {
			$cls = $this->_removeClassNameByPreg($name, $this->_attributes['class']);
		}
		else {
			$cls = '';
		}
		$cls .= (strlen($cls) ? ' ' : '') . $name;
		$this->setAttribute('class',$cls);
		return $this;
	}
	
	protected function _removeClassNameByPreg($name, $class) {
		$quoted = preg_quote($name);
		$new = preg_replace("/(^|\\s*)$quoted(\\s*|$)/", ' ', $class);
		$new = trim($new);
		return $new;
	}
	
	public function removeClassName($name) {
		if (isset($this->_attributes['class'])) {
			$new = $this->_removeClassNameByPreg($name, $this->_attributes['class']);
			if ($new == '') {
				$this->removeAttribute($name);
			}
			else {
				$this->setAttribute('class', trim($new));
			}
		}
		return $this;
	}
	
	public function hasClassName($name) {
		$quoted = preg_quote($name);
		$has = preg_match("/\\b$quoted\\b/",$this->_attributes['class']);
		return $has;
	}
	
	public function setStyle($p,$v) {
		$this->_style->$p = $v;
	}
	
	public function unsetStyle($p) {
		unset($this->_style->$p);
	}	
	
	public function __set($p, $v) {
		if ($p == 'innerHTML') {
			$this->_setInnerHTML($v);
			return;
		}
		if ($p == 'style') {
			$this->setAttribute('style', $v);
			return;
		}
		if (isset($this->_propertyMap[$p])) {
			$p = $this->_propertyMap[$p];
		}
		return $this->setAttribute($p, $v);
	}
	
	public function __get($p) {
		if ($p == 'innerHTML') {
			return $this->_getInnerHTML();
		}
		if ($p == 'style') {
			return $this->_style;
		}
		// check for namne in properties then attributes
		if (array_key_exists($p, $this->_properties)) {
			return $this->_properties[$p];
		}
		if (isset($this->_propertyMap[$p])) {
			$p = $this->_propertyMap[$p];
		}
		if ($p == 'tagName') {
			return strtoupper($this->_tagName);
		}
		if (array_key_exists($p, $this->_attributes)) {
			return $this->_attributes[$p];
		}
		return null;
	}
	
	protected function _getAttributeHtml() {
		$attr = $this->_attributes;
		$this->_processAttributes($attr);
		$html = '';
		foreach ($attr as $p => $v) {
			if (!is_object($v)) {
				// do we need to escape the property name?
				$html .= ' ' . $p . '="' . Quad_Esc::html($v) . '"';
			}
		}
		return $html;		
	}
	
	protected function _processAttributes(&$attr) {
		$styleStr = (string) $this->_style;
		if ($styleStr) {
			$attr['style'] = $styleStr;
		}
	}
		
	public function getOpenTag() {
		$html = "<$this->_tagName" . $this->_getAttributeHtml();
		$html .= ($this->selfClosing && self::$useXhtml ? " />" : '>');
		return $html;
	}
	
	public function getCloseTag() {
		if ($this->selfClosing) {
			$html = '';
		}
		else {
			$html = "</$this->_tagName>";
		}
		return $html;
	}
	
	public function addDecorator($name) {
		$this->_decorators[] = $name;
		return $this;
	}
	public function addDecorators($names) {
		foreach ($names as $name) {
			$this->addDecorator();
		}
		return $this;
	}
	public function removeDecorator($name) {
		foreach ($this->_decorators as $i => $dName) {
			if ($dName) {
				unset($this->_decorators[$i]);
				$this->_decorators = array_values($this->_decorators);
				return $this;
			}
		}
		return $this;
	}
	public function unsetDecorators() {
		$this->_decorators = array();
		return $this;
	}
	public function setDecorators($names) {
		$this->unsetDecorators();
		$this->addDecorators($names);
		return $this;
	}
	
	protected function _getInnerHTML() {
		return $this->nodeValue;
	}	
	
	protected function _setInnerHTML($value) {
		$this->nodeValue = $value;
	}	
		
	public function render() {
		if (count($this->_decorators)) {
			$done = array();
			$obj = $this;
			while (($dName = array_shift($this->_decorators))) {
				if (strpos($dName,'_') === false) {
					$cls = "Quad_Html_Decorator_" . ucwords($dName);
				}
				else {
					$cls = $dName;
				}
				// ensure class exists
				if (!class_exists($cls)) {
					trigger_error("Decorator class '$cls' not found.",E_USER_ERROR);
				}
				$tmp = new $cls($obj);
				if (!($tmp instanceof Quad_Html_Decorator_Interface)) {
					trigger_error("Decorator class '$cls' must implement Quad_Html_Decorator_Interface",E_USER_ERROR);
				}
				$obj = $tmp;
				array_unshift($done, $dName);
			}
			$html = $obj->render();
			unset($obj);
			$this->_decorators = $done;
			return $html;
		}
		else {
			$html = $this->getOpenTag();
			if (!$this->selfClosing) {
				$html .= $this->_getInnerHTML();
			}
			$html .= $this->getCloseTag();
			return $html;
		}		
	}
		
	public function __toString() {
		$html = $this->render();
		return $html;
	}
    
}
